﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.IO;
using System.Data;
using System.Collections;
using System.Xml.Linq;
using System.Linq;
using TSL_Tools.Utilities;
using System.Globalization;


namespace TSL_Tools.XMLTools
{
    /// <summary>
    /// <para>
    /// Look up a value handling by attrib or by tag (no translations). 
    /// First look to see if the tag exists, if not look for the value
    /// in the parent node attributes by var if the tag exists, look 
    /// for the value in the innerxml or by the attribute tag.
    /// </para>
    /// <example>
    ///     <Node Var=Value>
    ///     or
    ///     <Node>
    ///         <Var>Value</Var>
    ///     or
    ///     <Node>
    ///         <Var AttributeVar=Value />
    /// </example>
    /// </summary>
    public class XmlHelper
    {
        #region -- Public Properties ----------------------------------------------------       
        #endregion

        #region -- Private Properties ---------------------------------------------------
        #endregion

        #region -- Public Methods -------------------------------------------------------
        #region -- GetXmlValue -----
        public static string GetXmlValue(XmlNode nod, string strVar, string strAttributeVar, string strDefault)
        {
            // if null, tag doesn't exist so look for it by attribute on the main node
            return (nod[strVar] == null) ?
                        GetXmlAttributeValue(nod, strVar, strDefault) :
                        GetXmlAttributeValue(nod[strVar], strAttributeVar, nod[strVar].InnerXml.Trim());


        }

        public static string GetXmlValue(XmlNode nod, string strVar, string strDefault)
        {
            string strValue = GetXmlNodeValue(nod, strVar, strDefault);
            strValue = GetXmlAttributeValue(nod, strVar, strValue);
            return strValue;
        }

        public static bool  GetXmlValue(XmlNode nod, string strVar, bool bDefault=true)
        {
            string strValue = GetXmlNodeValue(nod, strVar, bDefault.ToString());
            strValue = GetXmlAttributeValue(nod, strVar, strValue);

            return bool.Parse(strValue);
        }

        public static int GetXmlValue(XmlNode nod, string strVar, int nDefault = 0)
        {
            string strValue = GetXmlNodeValue(nod, strVar, nDefault.ToString());
            strValue = GetXmlAttributeValue(nod, strVar, strValue);

            return int.Parse(strValue);
        }

        public static double GetXmlValue(XmlNode nod, string strVar, double dDefault = 0.0)
        {
            string strValue = GetXmlNodeValue(nod, strVar, dDefault.ToString());
            strValue = GetXmlAttributeValue(nod, strVar, strValue);

            return double.Parse(strValue);
        } 

        public static int GetXmlValue(XmlDocument xmlDoc, string strRoot, string strTag, string strID, string strVar, out string strValue)
        {
            XmlNode nod;
            string strXPath;
            strValue = "";



            //nod = m_doc.DocumentElement.SelectSingleNode("System[SystemID='"+strSystemID.Trim()+"']/"+strVar.Trim());
            strXPath = XPathCaseInsensitive(strRoot, strTag, strID) + "/" + strVar.Trim();
            nod = xmlDoc.DocumentElement.SelectSingleNode(strXPath);

            if (nod == null)
            {
                strValue = "";
                return -1;
            }
            else
            {
                strValue = nod.InnerXml.Trim();
            }

            return 0;
        }

        public static string GetXmlValue(XmlNode nod, string strVar)
        {
            return GetXmlValue(nod, strVar, null);
        }
        #endregion

        #region -- GetXmlNodeValue -----
        public static string GetXmlNodeValue(XmlNode nod, string strTag)
        {
            return GetXmlNodeValue(nod, strTag, "");
        }

        public static string GetXmlNodeValue(XmlNode nod, string strTag, string strDefault)
        {
            return (nod[strTag] == null) ? strDefault : nod[strTag].InnerXml.Trim();
        }
        #endregion

        #region -- GetXmlAttributeValue -----
        public static string GetXmlStringAttributeValue(XmlDocument doc, string nodeName, string nodeLookupAttributeName, string nodeLookupAttributeValue, string nodeWantedAttributeName)
        {
            return "";
        }

        public static string GetXmlAttributeValue(XmlNode nod, string strTag, string strDefault = "")
        {
            return (nod.Attributes[strTag] == null) ? strDefault : nod.Attributes[strTag].Value.Trim();
        }
        
        public static bool GetXmlAttributeValue(XmlNode nod, string strTag, bool bDefault = false)
        {
            if (nod.Attributes[strTag] == null)
                return bDefault;
            else
            {
                return (nod.Attributes[strTag].Value.Trim().ToLower() == "true") ? true : false;
            }
        }

        public static int GetXmlAttributeValue(XmlNode nod, string strTag, int iDefault = 0)
        {
            if (nod.Attributes[strTag] == null)
                return iDefault;
            else
            {
                return MyStringToInt(nod.Attributes[strTag].Value.Trim());
            }
        }

        public static double GetXmlAttributeValue(XmlNode nod, string strTag, double fDefault = 0)
        {
            if (nod.Attributes[strTag] == null)
                return fDefault;
            else
            {
                return MyStringToInt(nod.Attributes[strTag].Value.Trim());
            }
        }

        #endregion

        #region -- Load/Save Methods -----
        public static int LoadXMLString(XmlDocument doc, string rootTag, string strBuffer)
        {
            try
            {
                doc.LoadXml(strBuffer);
                LogMsg.Write(rootTag + " Initialized with Configuration string", true);
            }
            catch (Exception e)
            {
                LogMsg.Write("LoadXMLString:{0} Initialization Error, Unexpect exception loading Xml string {1}.  Exception {2}", rootTag, strBuffer, e.ToString());
                return -1;
            }

            // Make sure this is the proper type of config file by checking the header
            // The XSD file will probably check these things, but I want to be sure
            if (doc.DocumentElement.Name.CompareTo(rootTag) != 0)
            {
                LogMsg.Write("LoadXMLString:{0} Initialized with Invalid Configuration String {1}. Invalid Root Detected ", rootTag, strBuffer, doc.DocumentElement.Name);
                return -1;
            }
            return 0;

        }

        public static int LoadXMLFile(XmlDocument doc, string rootTag, string fileName)
        {
            //
            // Read a file
            try
            {

                doc.Load(fileName);
                LogMsg.Write(rootTag + " Initialized with Configuration File " + fileName, true);
            }
            catch (FileNotFoundException e)
            {
                LogMsg.Write("LoadXMLFile:{0} Initialization Error, File {1} not found.  Please check your config file.", rootTag, fileName, e.ToString());
                return -1;
            }
            catch (Exception e)
            {
                LogMsg.Write("LoadXMLFile:{0} Initialization Error, Unexpect exception loading Xml File {1}.  Exception {2}", rootTag, fileName, e.ToString());
                return -1;
            }

            // Make sure this is the proper type of config file by checking the header
            // The XSD file will probably check these things, but I want to be sure
            if (doc.DocumentElement.Name.CompareTo(rootTag) != 0)
            {
                LogMsg.Write("LoadXMLFile:{0} Initialized with Invalid Configuration File {1}. Invalid Root Detected ", rootTag, fileName, doc.DocumentElement.Name);
                return -1;
            }
            return 0;
        }

        public static bool SaveXmlFile(XmlDocument doc, string fileName)
        {
            //Not yet implemented
            return false;
        }

        ///// <summary>
        ///// Simple helper function to write out the Any File type item
        ///// use by object serialization functions
        ///// </summary>
        ///// <param name="w"></param>
        ///// <param name="strTag"></param>
        ///// <param name="fileName"></param>
        ///// <returns></returns>
        //static public void WriteFileElemXML(XmlWriter w, string strTag, string fileName)
        //{
        //    w.WriteStartElement(strTag);
        //    w.WriteAttributeString("File", fileName);
        //    w.WriteEndElement();

        //}
        #endregion

        #region -- XPath Methods -----
        /// <summary>
        /// Formats an Xpath command that is case insenstive
        /// </summary>
        /// <param name="strTag"></param>
        /// <param name="strValue"></param>
        /// <returns></returns>
        /// 
        public static string XPathCaseInsensitive(string strTag, string strValue)
        {
            return "translate(normalize-space(" + strTag + "), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = '" + strValue.Trim().ToLower() + "'";
        }
        public static string XPathCaseInsensitive(string strRoot, string strTag, string strValue)
        {
            return strRoot + "[" + XPathCaseInsensitive(strTag, strValue) + "]";
        }

        /// <summary>
        /// Not equal
        /// </summary>
        /// <param name="strTag"></param>
        /// <param name="strValue"></param>
        /// <returns></returns>
        public static string XPathCaseInsensitiveNotEqual(string strTag, string strValue)
        {
            return "translate(normalize-space(" + strTag + "), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') != '" + strValue.Trim().ToLower() + "'";
        }
        public static string XPathCaseInsensitiveNotEqual(string strRoot, string strTag, string strValue)
        {
            return strRoot + "[" + XPathCaseInsensitiveNotEqual(strTag, strValue) + "]";
        }
        #endregion

        public DataTable LoadSimpleXmlToDataTable(XmlDocument document, string xmlNode)
        {
            DataTable dataTable = new DataTable();

            return dataTable;
        }
        #endregion

        #region -- Private Methods ------------------------------------------------------
        private void LoadXmlDataToDataTable(ref DataTable table, ref XmlDocument document, string XmlNode, string XmlItem)
        {
            table.Clear();

            XmlNode nodRoot;

            if ((nodRoot = document.SelectSingleNode(XmlNode)) != null)
            {
                XmlNodeList nodList;
                if ((nodList = nodRoot.SelectNodes(XmlItem)) != null)
                {
                    foreach (XmlNode nod in nodList)
                    {
                        //Dictionary<string, string> items = ReadLeafNodes(nod);

                    }
                } // end if we have rules

            }
            /*
              <Inventory>   <---- XmlNode
               <Item>       <---- XmlItem
                <ItemID>200</ItemID>
                <ItemTypeID>B</ItemTypeID>
                <ColorID>0</ColorID>
                <ItemName>Idea Book 200</ItemName>
                <ItemTypeName>Book</ItemTypeName>
                <ColorName>(Not Applicable)</ColorName>
                <CategoryID>317</CategoryID>
                <CategoryName>Idea Book</CategoryName>
                <Status>I</Status>
                <Qty>1</Qty>
                <Price>25.000</Price>
                <Condition>U</Condition>
                <OrigPrice>0.000</OrigPrice>
                <OrigQty>0</OrigQty>
               </Item>
             */
        }
        #endregion

        private Dictionary<string, string> ReadLeafNodes(XmlNodeList nodList)
        {
            Dictionary<string, string> items = new Dictionary<string, string>();
            foreach(XmlNode nod in nodList)
            {
                if(!items.ContainsKey(nod.LocalName))
                {
                    items.Add(nod.LocalName, nod.Value);
                }
            }
            return items;
        }

        private static int MyStringToInt(string inputStr)
        {
            int x;
            if (!Int32.TryParse(inputStr, out x))
            {
                throw new InvalidCastException(String.Format(CultureInfo.InvariantCulture, "Failed to cast string to int. String Value='{0}'", inputStr));
            }
            return x;
        }

        private static double MyStringToDouble(string inputStr)
        {
            double x;
            if (!Double.TryParse(inputStr, out x))
            {
                throw new InvalidCastException(String.Format(CultureInfo.InvariantCulture, "Failed to cast string to double. String Value='{0}'", inputStr));
            }
            return x;
        }
    }

    public class ParseXmlForLeafNodes
    {
        public Dictionary<string, string> leafNodes { get; set; }
        public bool ContainsDuplicateKeys = false;

        public ParseXmlForLeafNodes()
        {
            leafNodes = new Dictionary<string, string>();
        }

        public int BuildLeafList(string payLoad)
        {
            int x = 0;
            ContainsDuplicateKeys = false;

            XDocument xDoc = XDocument.Parse(payLoad);

            var leaves =
                from e in xDoc.Descendants()
                where !e.Elements().Any()
                select e;

            foreach (XElement element in leaves)
            {
                if (!leafNodes.ContainsKey(element.Name.LocalName))
                {
                    leafNodes.Add(element.Name.LocalName, element.Value);
                    x++;
                }
                else
                {
                    ContainsDuplicateKeys = true;
                }
            }
            return x;
        }
    }

    public class XmlLeafNodeItemList : IEnumerable
    {
        #region -- Public Properties ----------------------------------------------------
        public XmlLeafNodeItem this[string strID]
        {
            get { return _itemList[strID]; }
            set { Add(value); }

        }
        #endregion

        #region -- Private Properties ---------------------------------------------------
        private Dictionary<string, XmlLeafNodeItem> _itemList;
        #endregion

        #region -- Constructors ---------------------------------------------------------
        public XmlLeafNodeItemList()
        {
            _itemList = new Dictionary<string, XmlLeafNodeItem>(StringComparer.CurrentCultureIgnoreCase);
        }
        #endregion

        #region -- Public Methods -------------------------------------------------------
        public void Add(XmlLeafNodeItem _item)
        {
            _itemList[_item.ContextId] = _item;
        }

        public bool Add(string contextId, string contextName, string xmlLeafName = "", string defaultValue = "", string xmlParentPath = "", bool isPreDefinedItem = false)
        {
            if (_itemList.ContainsKey(contextId))
            {
                return false;
            }
            XmlLeafNodeItem _item = new XmlLeafNodeItem();
            _item.ContextId = contextId;
            _item.ContextName = contextName;
            _item.XmlLeafName = xmlLeafName;
            _item.DefaultValue = defaultValue;
            _item.XmlParentPath = xmlParentPath;
            _item.IsPreDefinedItem = isPreDefinedItem;

            _itemList.Add(contextId, _item);
            return true;
        }
        #endregion

        #region -- Private Methods ------------------------------------------------------
        #endregion

        #region -- Enumeration -----------------------------------------------------
        public IEnumerator GetEnumerator() { return new ContextsEnumerator(_itemList); }

        private class ContextsEnumerator : IEnumerator, IDisposable
        {
            private Dictionary<string, XmlLeafNodeItem>.Enumerator _Enumerator;

            public object Current { get { return _Enumerator.Current.Value; } }
            public void Dispose() { _Enumerator.Dispose(); }
            public bool MoveNext() { return _Enumerator.MoveNext(); }
            public void Reset() { throw new NotImplementedException("Reset not implmented"); }


            public ContextsEnumerator(Dictionary<string, XmlLeafNodeItem> dictionary)
            {
                _Enumerator = dictionary.GetEnumerator();
            }

        } // end class 
        #endregion
    }

    public class XmlLeafNodeItem
    {
        #region -- Public Properties ----------------------------------------------------
        /// <summary>
        /// The ID of the context in the list of contexts
        /// </summary>
        public string ContextId { get; set; }

        /// <summary>
        /// The name of the context parameter holding the data
        /// </summary>
        public string ContextName { get; set; }

        /// <summary>
        /// The XML NODE that has a value
        /// </summary>
        public string XmlLeafName { get; set; }

        /// <summary>
        /// The value that was discovered in the leaf node during parsing, or the value that 
        /// was PreDefined if this is an expected node
        /// </summary>
        public string DefaultValue { get; set; }

        /// <summary>
        /// the path up to the root of the payload where this leaf resides
        /// </summary>
        public string XmlParentPath { get; set; }

        /// <summary>
        /// Was this leaf an expected node with PreDefined info, or was it discovered via the parsing.
        /// </summary>
        public bool IsPreDefinedItem { get; set; }
        #endregion

        #region -- Constructors ---------------------------------------------------------
        #endregion

        #region -- Public Methods -------------------------------------------------------
        #endregion

        #region -- Private Methods ------------------------------------------------------
        #endregion

    }
}
